#ifndef __TDP2_HASH_HPP__
#define __TDP2_HASH_HPP__

#include "tdp2.hpp"

namespace TDP
{
	namespace Crypto
	{
		inline UInt32 ShiftRightRotate(UInt32 v, int count)
		{
			return (v >> count) | (v << (32 - count));
		}

		namespace SHA256
		{
			class SHA256State
			{
			private:
				UInt32 _data[8];
				UInt8 _pending[64];
				int _numPending;
				void FullCycle();
				void Cycle(UInt32 seed, int round);

			public:
				SHA256State();
				void Terminate();
				void FeedBytes(const void *ptr, LargeUInt n);
				void Flush(UInt8 *outBytes);
				void Flush(char *outChars);
				void Flush(UInt32 *outU32);
			};
		}
	};


};

namespace TDP
{
	namespace Crypto
	{
		namespace SHA256
		{
			inline SHA256State::SHA256State()
			{
				_data[0] = 0x6a09e667;
				_data[1] = 0xbb67ae85;
				_data[2] = 0x3c6ef372;
				_data[3] = 0xa54ff53a;
				_data[4] = 0x510e527f;
				_data[5] = 0x9b05688c;
				_data[6] = 0x1f83d9ab;
				_data[7] = 0x5be0cd19;
				_numPending = 0;
			}

			inline void SHA256State::Cycle(UInt32 seed, int round)
			{
				static const UInt32 k[] = {
					0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
					0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
					0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
					0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
					0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
					0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
					0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
					0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };

				UInt32 ch = (_data[4] & _data[5]) ^ ((~_data[4]) & _data[6]);
				UInt32 s1 = ShiftRightRotate(_data[4], 6) ^ ShiftRightRotate(_data[4], 11) ^ ShiftRightRotate(_data[4], 25);
				UInt32 combine1 = k[round] + seed + ch + _data[7] + s1;
				UInt32 ma = (_data[0] & _data[1]) ^ (_data[0] & _data[2]) ^ (_data[1] & _data[2]);
				UInt32 s0 = ShiftRightRotate(_data[0], 2) ^ ShiftRightRotate(_data[0], 13) ^ ShiftRightRotate(_data[0], 22);
				_data[7] = _data[6];
				_data[6] = _data[5];
				_data[5] = _data[4];
				_data[4] = _data[3] + combine1;
				_data[3] = _data[2];
				_data[2] = _data[1];
				_data[1] = _data[0];
				_data[0] = combine1 + ma + s0;
			}

			inline void SHA256State::FullCycle()
			{
				UInt32 seeds[64];
				
				for(int i=0;i<16;i++)
					seeds[i] = _pending[i*4+0] | (_pending[i*4+1] << 8) | (_pending[i*4+2] << 16) | (_pending[i*4+3] << 24);
				for(int i=16;i<64;i++)
				{
					UInt32 s0 = (ShiftRightRotate(seeds[i-15], 7) ^ ShiftRightRotate(seeds[i-15], 18) ^ ShiftRightRotate(seeds[i-15], 3));
					UInt32 s1 = (ShiftRightRotate(seeds[i-2], 17) ^ ShiftRightRotate(seeds[i-2], 19) ^ ShiftRightRotate(seeds[i-2], 10));
					seeds[i] = seeds[i-16] + seeds[i-7] + s0 + s1;
				}

				for(int i=0;i<64;i++)
					Cycle(seeds[i], i);
			}

			inline void SHA256State::FeedBytes(const void *ptr, LargeUInt n)
			{
				const UInt8 *cptr = static_cast<const UInt8 *>(ptr);
				while(n > 0)
				{
					_pending[_numPending++] = *cptr++;
					n--;

					if(_numPending == 64)
					{
						this->FullCycle();
						_numPending = 0;
					}
				}
			}

			inline void SHA256State::Terminate()
			{
				static const UInt8 shaTerminator[] = {
					0x24, 0x3F, 0x6A, 0x88, 0x85, 0xA3, 0x08, 0xD3,
					0x13, 0x19, 0x8A, 0x2E, 0x03, 0x70, 0x73, 0x44,
					0xA4, 0x09, 0x38, 0x22, 0x29, 0x9F, 0x31, 0xD0,
					0x08, 0x2E, 0xFA, 0x98, 0xEC, 0x4E, 0x6C, 0x89,
					0x45, 0x28, 0x21, 0xE6, 0x38, 0xD0, 0x13, 0x77,
					0xBE, 0x54, 0x66, 0xCF, 0x34, 0xE9, 0x0C, 0x6C,
					0xC0, 0xAC, 0x29, 0xB7, 0xC9, 0x7C, 0x50, 0xDD,
					0x3F, 0x84, 0xD5, 0xB5, 0xB5, 0x47, 0x09, 0x17
				};
				FeedBytes(shaTerminator, 64);
			}

			inline void SHA256State::Flush(UInt32 *outU32)
			{
				if(_numPending != 0)
				{
					int offs = 0;
					while(_numPending < 64)
						_pending[_numPending++] = _pending[offs++];
					this->FullCycle();
				}

				for(int i=0;i<8;i++)
					outU32[i] = _data[i];
			}

			inline void SHA256State::Flush(UInt8 *bytesout)
			{
				UInt32 data[8];
				Flush(data);
				for(int i=0;i<32;i++)
					bytesout[i] = static_cast<UInt8>( (data[i/4] >> ((i%4)*8)) & 0xff );
			}

			inline void SHA256State::Flush(char *stringout)
			{
				UInt32 data[8];
				Flush(data);
				
				static const char *nibbles = "0123456789abcdef";
				for(int i=0;i<64;i++)
					stringout[i] = nibbles[ (data[i/8] >> ((i%8)*4)) & 0xf ];
			}
		}
	}

	inline UInt32 Hash32(UInt32 hash, const void *ptr, LargeUInt n)
	{
		const char *str = (const char *)ptr;

		while(n)
		{
			hash += ((~hash) << 13) | ((hash >> 19) & 0x7FFFF);
			hash += *str++;
			n--;
		}

		return hash;
	}
}

#endif
